﻿using eX_Snake_Rewrite.Properties;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace eX_Snake_Rewrite
{
    class Program
    {
        #region P/Invoke

        [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        private static extern IntPtr GetForegroundWindow();

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);

        #endregion

        #region Key Events

        /// <summary>
        /// Codes representing keyboard keys.
        /// </summary>
        /// <remarks>
        /// Key code documentation:
        /// http://msdn.microsoft.com/en-us/library/dd375731%28v=VS.85%29.aspx
        /// </remarks>
        internal enum KeyCode : int
        {
            /// <summary>
            /// The left arrow key.
            /// </summary>
            Left = 0x25,

            /// <summary>
            /// The up arrow key.
            /// </summary>
            Up,

            /// <summary>
            /// The right arrow key.
            /// </summary>
            Right,

            /// <summary>
            /// The down arrow key.
            /// </summary>
            Down,

            /// <summary>
            /// The escape key.
            /// </summary>
            Escape = 0x1B,

            /// <summary>
            /// The enter key.
            /// </summary>
            Enter = 0x0D
        }

        /// <summary>
        /// Provides keyboard access.
        /// </summary>
        internal static class NativeKeyboard
        {
            /// <summary>
            /// A positional bit flag indicating the part of a key state denoting
            /// key pressed.
            /// </summary>
            private const int KeyPressed = 0x8000;

            /// <summary>
            /// Returns a value indicating if a given key is pressed.
            /// </summary>
            /// <param name="key">The key to check.</param>
            /// <returns>
            /// <c>true</c> if the key is pressed, otherwise <c>false</c>.
            /// </returns>
            public static bool IsKeyDown(KeyCode key)
            {
                return (GetKeyState((int)key) & KeyPressed) != 0;
            }

            /// <summary>
            /// Gets the key state of a key.
            /// </summary>
            /// <param name="key">Virtuak-key code for key.</param>
            /// <returns>The state of the key.</returns>
            [System.Runtime.InteropServices.DllImport("user32.dll")]
            private static extern short GetKeyState(int key);
        }

        #endregion

        #region Objects

        class Snake
        {
            public List<Point> Body;
            public ConsoleColor BodyColor;
            public ConsoleColor BackColor;

            /*
             * http://en.wikipedia.org/wiki/Box-drawing_characters
             */
            public char[] IBodyChars = {
                '*',           //..Unified
                (char)0x2500,  //..Up
                (char)0x2500,  //..Down
                (char)0x2503,  //..Left
                (char)0x2503,  //..Right
                (char)0x250C,  //..Corner: TURN ^ >
                (char)0x2510,  //..Corner: TURN ^ <
                (char)0x2514,  //..Corner: TURN . >
                (char)0x2518,  //..Corner: TURN . <
            };

            public Snake(List<Point> nBody = null, int initSize = 5,
                ConsoleColor initBodyColor = ConsoleColor.Gray,
                ConsoleColor initBackColor = ConsoleColor.Black)
            {
                if (nBody == null)
                {
                    Body = new List<Point>();
                    for (int i = 0; i < initSize; ++i)
                    {
                        Body.Add(new Point(5, 2 + i));
                    }
                }
                else
                {
                    Body = nBody;
                }

                BodyColor = initBodyColor;
                BackColor = initBackColor;
            }
        }

        class Food
        {
            public Point Position;
            public ConsoleColor BodyColor;
            public ConsoleColor BackColor;
            public char IFoodChar = 'o';

            public Food(ConsoleColor initBodyColor = ConsoleColor.Gray, 
                ConsoleColor initBackColor = ConsoleColor.Black)
            {
                BodyColor = initBodyColor;
                BackColor = initBackColor;
            }
        }

        #endregion

        #region Globals

        static Snake iSnake;
        static Food iFood;
        static Boolean isExit = false;
        static Boolean isOver = false;
        static Boolean Paused = false;
        static Int32 RealHeight = 0;
        static Int32 RealWidth = 0;
        static Int32 FeedCount = 0;
        static Int32 RendCount = 0;
        static Int32 CyclCount = 0;
        static Int32 QueueInteger = 0;
        static Int32 QueueMaximum = 5; //Logs position only once every 5 ticks
        static Int32 Score;
        static Int32 Speed;
        static Int32 Size;
        static Direction Way = Direction.Right;
        static Modes Mode;
        static Difficulty Diff;
        static Random Rand = new Random();
        static System.Timers.Timer DirT = new System.Timers.Timer();
        static Dictionary<Point, String> bShiiFT = new Dictionary<Point, String>();
        static Point NullPoint = new Point(-1, -1);

        enum Direction { Left, Right, Up, Down }
        enum Modes { Infinate, Classic, Custom }
        enum DialogMode { Dead, Pause, Loading }
        enum Difficulty { Easy, Medium, Hard, Extreme, Insane, Custom }

        static int HighScore
        {
            get
            {
                if (Diff == Difficulty.Easy) { return Settings.Default.HS_Easy; }
                else if (Diff == Difficulty.Medium) { return Settings.Default.HS_Medium; }
                else if (Diff == Difficulty.Hard) { return Settings.Default.HS_Hard; }
                else if (Diff == Difficulty.Extreme) { return Settings.Default.HS_Extreme; }
                else if (Diff == Difficulty.Insane) { return Settings.Default.HS_Insane; }
                else if (Diff == Difficulty.Custom) { return Settings.Default.HS_Custom; }
                else { return 0; }
            }
            set
            {
                if (Diff == Difficulty.Easy) { Settings.Default.HS_Easy = value; }
                else if (Diff == Difficulty.Medium) { Settings.Default.HS_Medium = value; }
                else if (Diff == Difficulty.Hard) { Settings.Default.HS_Hard = value; }
                else if (Diff == Difficulty.Extreme) { Settings.Default.HS_Extreme = value; }
                else if (Diff == Difficulty.Insane) { Settings.Default.HS_Insane = value; }
                else if (Diff == Difficulty.Custom) { Settings.Default.HS_Custom = value; }
            }
        }

        #endregion

        static void Main(string[] args)
        {
            #region Console Properties

            Console.Title = "exSnake";
            Console.CursorVisible = false;
            Console.SetWindowSize(60, 30);
            Console.BufferHeight = 30;
            Console.BufferWidth = 60;
            RealHeight = Console.BufferHeight - 2;
            RealWidth = Console.BufferWidth - 1;

            #endregion

            Paused = false;
            isOver = false;
            Speed = 100; //MIN: 50 //MAX: 1000 || Less = Faster = Harder
            Load();
            
            while (!isExit)
            {
                if (!Paused)
                {
                    Render();
                    UpBar();
                }

                Thread.Sleep(Speed);
                CyclCount++;
            }
        }

        #region Game Functions
        
        static void Load()
        {
            Paused = true;
            DirT.Stop();
            ConsoleDialog("Loading..", DialogMode.Loading);
            iSnake = new Snake(null, 5, ConsoleColor.Cyan);
            DetectDirection();
            Thread.Sleep(1000);
            Feed();
            Resume();
            DirT.Start();
            Paused = false;
        }

        static void Reset()
        {
            Console.Clear();
            Score = 0;
            Size = 0;
            Way = Direction.Right;

            iSnake = new Snake(null, 5, ConsoleColor.Cyan);
            Feed();

            Paused = false;
            isOver = false;
        }

        static void Resume()
        {
            Console.Clear();
            Paused = false;
            foreach (Point x in iSnake.Body) { WriteConsole(x, iSnake.IBodyChars[0].ToString(), iSnake.BodyColor, iSnake.BackColor); }
            WriteConsole(iFood.Position, iFood.IFoodChar.ToString(), iFood.BodyColor, iFood.BackColor);
        }

        #endregion

        #region Info Bar

        static void UpBar()
        {
            /*
             * Update Size, Score, etc in seperate thread
             */
            ThreadPool.QueueUserWorkItem(delegate
            {
                String toWrite = string.Format("Size {0} | Score {1} | High Score {2}", iSnake.Body.Count, Score, HighScore);
                WriteConsole(new Point(Console.BufferWidth / 2 - toWrite.Length / 2, Console.BufferHeight - 1), toWrite, ConsoleColor.Black, ConsoleColor.Gray);

            }, null);
        }

        #endregion

        #region Animation

        static void Render()
        {
            Point toRem = iSnake.Body[iSnake.Body.Count - 1];
            WriteConsole(toRem, " ");
            iSnake.Body.Remove(toRem);
            Point next = iSnake.Body[0];

            #region Direction Changing

            if (Way == Direction.Down)
            {
                next = new Point(next.X, next.Y + 1);
            }
            else if (Way == Direction.Up)
            {
                next = new Point(next.X, next.Y - 1);
            }
            else if (Way == Direction.Left)
            {
                next = new Point(next.X - 1, next.Y);
            }
            else if (Way == Direction.Right)
            {
                next = new Point(next.X + 1, next.Y);
            }

            iSnake.Body.Insert(0, next);

            #endregion

            #region Collision Detection

            if ((int)next.X > RealWidth)
            {
                //WALL: RIGHT
                ConsoleDialog("You ran into the right wall!");
                return;
            }
            else if ((int)next.Y > RealHeight)
            {
                //WALL: BOTTOM
                ConsoleDialog("You ran into the bottom wall!");
                return;
            }
            else if ((int)next.X <= -1)
            {
                //WALL: LEFT
                ConsoleDialog("You ran into the left wall!");
                return;
            }
            else if ((int)next.Y <= -1)
            {
                //WALL: TOP
                ConsoleDialog("You ran into the top wall!");
                return;
            }
            else
            {
                /*
                 * Has head intersected with body
                 */
                foreach (Point i in iSnake.Body.GetRange(1, iSnake.Body.Count - 1))
                {
                    if (iSnake.Body[0] == i)
                    {
                        ConsoleDialog("You ran into yourself!");
                        return;
                    }
                }

                /*
                 * Has head intersected with food
                 */
                if (iSnake.Body[0] == iFood.Position)
                {
                    Point x = iSnake.Body[iSnake.Body.Count - 1];
                    iSnake.Body.Add(new Point((int)x.X, (int)x.Y));
                    Score++;
                    if (Score > HighScore) { HighScore = Score; }
                    Feed();
                }
            }

            WriteConsole(next, iSnake.IBodyChars[0].ToString(), iSnake.BodyColor);

            #endregion

            RendCount++;
        }

        #endregion

        #region Generate Food

        static void Feed()
        {
            iFood = new Food(ConsoleColor.Red);
            iFood.Position = new Point(Rand.Next(0, RealWidth), Rand.Next(0, RealHeight));
            Boolean _FLAG = false;

            /*
             * Make sure food does not appear in snake body
             */
            foreach (Point i in iSnake.Body)
            {
                if (iFood.Position == i)
                {
                    _FLAG = true;
                }
            }

            if (!_FLAG)
            {
                WriteConsole(iFood.Position, iFood.IFoodChar.ToString(), iFood.BodyColor);
            }
            else
            {
                Feed();
            }

            FeedCount++;
        }

        #endregion

        #region Forgeground Check

        public static bool ApplicationIsActivated()
        {
            var activatedHandle = GetForegroundWindow();
            if (activatedHandle == IntPtr.Zero)
            {
                return false;
            }

            var procId = Process.GetCurrentProcess().Id;
            int activeProcId;
            GetWindowThreadProcessId(activatedHandle, out activeProcId);

            return activeProcId == procId;
        }

        #endregion

        #region Key Events

        static void DetectDirection()
        {
            DirT = new System.Timers.Timer()
            {
                Interval = 1,
                Enabled = true
            };

            DirT.Elapsed += delegate
            {
                if (ApplicationIsActivated())
                {
                    if ((NativeKeyboard.IsKeyDown(KeyCode.Down)) && (!Paused))
                    {
                        if (Way != Direction.Up)
                        {
                            Way = Direction.Down;
                            return;
                        }
                    }
                    else if ((NativeKeyboard.IsKeyDown(KeyCode.Up)) && (!Paused))
                    {
                        if (Way != Direction.Down)
                        {
                            Way = Direction.Up;
                            return;
                        }
                    }
                    else if ((NativeKeyboard.IsKeyDown(KeyCode.Left)) && (!Paused))
                    {
                        if (Way != Direction.Right)
                        {
                            Way = Direction.Left;
                            return;
                        }
                    }
                    else if ((NativeKeyboard.IsKeyDown(KeyCode.Right)) && (!Paused))
                    {
                        if (Way != Direction.Left)
                        {
                            Way = Direction.Right;
                            return;
                        }
                    }
                    else if (NativeKeyboard.IsKeyDown(KeyCode.Escape))
                    {
                        if (!isOver)
                        {
                            if (!Paused)
                            {
                                Paused = true;
                                ConsoleDialog("Game Paused", DialogMode.Pause);
                                UpBar();
                                return;
                            }
                        }
                        else
                        {
                            isExit = true;
                            return;
                        }
                    }
                    else if (NativeKeyboard.IsKeyDown(KeyCode.Enter))
                    {
                        if (isOver)
                        {
                            Console.Clear();
                            Reset();
                            return;
                        }
                        else
                        {
                            if (Paused)
                            {
                                Resume();
                                return;
                            }
                        }
                    }
                }
            };

            DirT.Start();
        }

        #endregion

        #region Adapters

        static void WriteConsole(Point Where, String toWrite, ConsoleColor FColor = ConsoleColor.Gray, ConsoleColor BColor = ConsoleColor.Black)
        {
            ConsoleColor RFColor = Console.ForegroundColor;
            ConsoleColor BFColor = Console.BackgroundColor;
            Point RPosition = new Point(Console.CursorLeft, Console.CursorTop);
            Console.ForegroundColor = FColor;
            Console.BackgroundColor = BColor;
            try { Console.SetCursorPosition((int)Where.X, (int)Where.Y); } catch {  }
            Console.Write(toWrite);
            Console.ForegroundColor = RFColor;
            Console.BackgroundColor = BFColor;
            Console.SetCursorPosition((int)RPosition.X, (int)RPosition.Y);
        }

        #endregion

        #region Console Controls

        static void ConsoleDialog(String Msg = "You Died!", DialogMode Mode = DialogMode.Dead, ConsoleColor FillColor = ConsoleColor.Gray)
        {
            DirT.Stop();
            Paused = true;
            String AskQuitMsg = "";
            if (Mode == DialogMode.Dead) { AskQuitMsg = "Press {Enter} to retry, or {Esc} to exit"; isOver = true; }
            if (Mode == DialogMode.Pause) { AskQuitMsg = "Press {Enter} to continue"; isOver = false; }
            if (Mode == DialogMode.Loading) { isOver = false; }

            #region Search & Destroy

            if (Mode != DialogMode.Loading)
            {
                foreach (Point p in iSnake.Body.GetRange(1, iSnake.Body.Count - 1))
                {
                    WriteConsole(p, " ");
                }

                WriteConsole(iFood.Position, " ");
            }

            #endregion

            for (int i = 0; i < Console.BufferHeight; ++i)
            {
                for (int x = 0; x < Console.BufferWidth; ++x)
                {
                    WriteConsole(new Point(x, i), ((char)0x2592).ToString(), ConsoleColor.Black, FillColor);
                }
            }

            WriteConsole(new Point(Console.BufferWidth / 2 - Msg.Length / 2, Console.BufferHeight / 2 - 1), Msg, ConsoleColor.Black, FillColor);
            WriteConsole(new Point(Console.BufferWidth / 2 - AskQuitMsg.Length / 2, Console.BufferHeight / 2 + 1), AskQuitMsg, ConsoleColor.Black, FillColor);
            DirT.Start();
        }

        #endregion
    }
}
